/*
 * Copyright (c) 2011-2013, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.transform.wavelet;

import boofcv.alg.InputSanityCheck;
import boofcv.alg.misc.PixelMath;
import boofcv.alg.transform.wavelet.impl.ImplWaveletTransformBorder;
import boofcv.alg.transform.wavelet.impl.ImplWaveletTransformInner;
import boofcv.alg.transform.wavelet.impl.ImplWaveletTransformNaive;
import boofcv.struct.image.ImageFloat32;
import boofcv.struct.image.ImageSInt32;
import boofcv.struct.wavelet.WaveletDescription;
import boofcv.struct.wavelet.WlCoef_F32;
import boofcv.struct.wavelet.WlCoef_I32;

/**
 * <p>
 * Functional interface for applying general purpose wavelet and inverse wavelet transforms.
 * </p>
 *
 * <p>
 * A single level wavelet transform breaks the image up into four regions:
 * <table border="1">
 * <tr><td>a</td><td>h</td></tr>
 * <tr><td>v</td><td>d</td></tr>
 * </table>
 * Each region has M/2,N/2 rows and columns. Region 'a' is the scaling image, 'h' and 'v' are
 * a combination of scaling and wavelet, and 'd' is a combination of horizontal and vertical wavelets.
 * When a multiple level transform is performed then the input to the next level is the 'a' from the previous
 * level.
 * </p>
 *
 * <p>
 * DO NOT MODIFY: This class was automatically generated by {@link boofcv.alg.transform.wavelet.GenerateWaveletTransformOps}
 * </p>
 *
 * @author Peter Abeles
 */
public class WaveletTransformOps {

	/**
	 * <p>
	 * Performs a single level wavelet transform.
	 * </p>
	 *
	 * @param desc Description of the wavelet.
	 * @param input Input image. Not modified.
	 * @param output Where the wavelet transform is written to. Modified.
	 * @param storage Optional storage image.  Should be the same size as output image. If null then
	 * an image is declared internally.
	 */
	public static void transform1( WaveletDescription<WlCoef_F32> desc ,
								   ImageFloat32 input , ImageFloat32 output ,
								   ImageFloat32 storage )
	{
		UtilWavelet.checkShape(input,output);

		WlCoef_F32 coef = desc.getForward();

		if( output.width < coef.scaling.length || output.width < coef.wavelet.length )
			throw new IllegalArgumentException("Wavelet is too large for provided image.");
		if( output.height < coef.scaling.length || output.height < coef.wavelet.length )
			throw new IllegalArgumentException("Wavelet is too large for provided image.");
		storage = InputSanityCheck.checkDeclare(output, storage);

		// the faster routines can only be run on images which are not too small
		int minSize = Math.max(coef.getScalingLength(),coef.getWaveletLength())*3;

		if( input.getWidth() <= minSize || input.getHeight() <= minSize ) {
			ImplWaveletTransformNaive.horizontal(desc.getBorder(),coef,input,storage);
			ImplWaveletTransformNaive.vertical(desc.getBorder(),coef,storage,output);
		} else {
			ImplWaveletTransformInner.horizontal(coef,input,storage);
			ImplWaveletTransformBorder.horizontal(desc.getBorder(),coef,input,storage);
			ImplWaveletTransformInner.vertical(coef,storage,output);
			ImplWaveletTransformBorder.vertical(desc.getBorder(),coef,storage,output);
		}
	}

	/**
	 * <p>
	 * Performs a level N wavelet transform using the fast wavelet transform (FWT).
	 * </p>
	 *
	 * <p>To save memory the input image is used to store intermediate results and is modified.</p>
	 *
	 * @param desc Description of the wavelet.
	 * @param input Input image and is used as internal workspace. Modified.
	 * @param output Where the multilevel wavelet transform is written to. Modified.
	 * @param storage Optional storage image.  Should be the same size as output image. If null then
	 * an image is declared internally.
	 * @param numLevels Number of levels which should be computed in the transform.
	 */
	public static void transformN( WaveletDescription<WlCoef_F32> desc ,
								   ImageFloat32 input , ImageFloat32 output ,
								   ImageFloat32 storage ,
								   int numLevels )
	{
		if( numLevels == 1 ) {
			transform1(desc,input,output, storage);
			return;
		}

		UtilWavelet.checkShape(desc.getForward(),input,output,numLevels);
		storage = InputSanityCheck.checkDeclare(output, storage);
		// modify the shape of a temporary image not the original
		storage = storage.subimage(0,0,output.width,output.height, null);
		storage.subImage = false;

		transform1(desc,input,output, storage);

		for( int i = 2; i <= numLevels; i++ ) {
			int width = output.width/2;
			int height = output.height/2;
			width += width%2;
			height += height%2;

			input = input.subimage(0,0,width,height, null);
			output = output.subimage(0,0,width,height, null);
			input.setTo(output);

			// transform the scaling image and save the results in the output image
			storage.reshape(width,height);
			transform1(desc,input,output,storage);
		}
	}

	/**
	 * <p>
	 * Performs a single level inverse wavelet transform. Do not pass in a whole image which has been
	 * transformed by a multilevel transform.  Just the relevant sub-image.
	 * </p>
	 *
	 * @param desc Description of the inverse wavelet.
	 * @param input Input wavelet transform. Not modified.
	 * @param output Reconstruction of original image. Modified.
	 * @param storage Optional storage image.  Should be the same size as the input image. If null then
	 * an image is declared internally.
	 * @param minValue Minimum allowed pixel value
	 * @param maxValue Maximum allowed pixel value
	 */
	public static void inverse1( WaveletDescription<WlCoef_F32> desc ,
								 ImageFloat32 input , ImageFloat32 output ,
								 ImageFloat32 storage , float minValue , float maxValue )
	{
		UtilWavelet.checkShape(output,input);
		WlCoef_F32 coef = desc.getForward();
		if( output.width < coef.scaling.length || output.width < coef.wavelet.length )
			throw new IllegalArgumentException("Wavelet is too large for provided image.");
		if( output.height < coef.scaling.length || output.height < coef.wavelet.length )
			throw new IllegalArgumentException("Wavelet is too large for provided image.");
		storage = InputSanityCheck.checkDeclare(input, storage);

		// the faster routines can only be run on images which are not too small
		int minSize = Math.max(coef.getScalingLength(),coef.getWaveletLength())*3;

		if( output.getWidth() <= minSize || output.getHeight() <= minSize ) {
			ImplWaveletTransformNaive.verticalInverse(desc.getBorder(),desc.getInverse(),input,storage);
			ImplWaveletTransformNaive.horizontalInverse(desc.getBorder(),desc.getInverse(),storage,output);
		} else {
			ImplWaveletTransformInner.verticalInverse(desc.getInverse().getInnerCoefficients(),input,storage);
			ImplWaveletTransformBorder.verticalInverse(desc.getBorder(),desc.getInverse(),input,storage);
			ImplWaveletTransformInner.horizontalInverse(desc.getInverse().getInnerCoefficients(),storage,output);
			ImplWaveletTransformBorder.horizontalInverse(desc.getBorder(),desc.getInverse(),storage,output);
		}

		if( minValue != -Float.MAX_VALUE && maxValue != Float.MAX_VALUE )
			PixelMath.boundImage(output,minValue,maxValue);
	}

	/**
	 * <p>Performs a level N inverse fast wavelet transform (FWT).</p>
	 *
	 * <p>To save memory the input image is used to store intermediate results and is modified.</p>
	 *
	 * @param desc Description of the inverse wavelet.
	 * @param input Input wavelet transform and is used as internal workspace. Modified.
	 * @param output Reconstruction of original image. Modified.
	 * @param storage Optional storage image.  Should be the same size as the input image. If null then
	 * an image is declared internally.
	 * @param numLevels Number of levels in the transform.
	 * @param minValue Minimum allowed pixel value
	 * @param maxValue Maximum allowed pixel value
	 */
	public static void inverseN( WaveletDescription<WlCoef_F32> desc ,
								 ImageFloat32 input , ImageFloat32 output ,
								 ImageFloat32 storage,
								 int numLevels ,
								 float minValue , float maxValue)
	{
		if( numLevels == 1 ) {
			inverse1(desc,input,output, storage,minValue,maxValue);
			PixelMath.boundImage(output, minValue, maxValue);
			return;
		}

		UtilWavelet.checkShape(desc.getForward(),output,input,numLevels);
		storage = InputSanityCheck.checkDeclare(input, storage);
		// modify the shape of a temporary image not the original
		storage = storage.subimage(0,0,input.width,input.height, null);
		storage.subImage = false;

		int width,height;

		int scale = UtilWavelet.computeScale(numLevels);
		width = input.width/scale;
		height = input.height/scale;
		width += width%2;
		height += height%2;

		ImageFloat32 levelIn = input.subimage(0,0,width,height, null);
		ImageFloat32 levelOut = output.subimage(0,0,width,height, null);
		storage.reshape(width,height);
		inverse1(desc,levelIn,levelOut, storage,-Float.MAX_VALUE,Float.MAX_VALUE);

		for( int i = numLevels-1; i >= 1; i-- ) {
			// copy the decoded segment into the input
			levelIn.setTo(levelOut);
			if( i > 1 ) {
				scale /= 2;
				width = input.width/scale;
				height = input.height/scale;
				width += width%2;
				height += height%2;

				storage.reshape(width,height);
				levelIn = input.subimage(0,0,width,height, null);
				levelOut = output.subimage(0,0,width,height, null);
			} else {
				levelIn = input;
				levelOut = output;
			}

			storage.reshape(levelIn.width,levelIn.height);
			inverse1(desc,levelIn,levelOut, storage,-Float.MAX_VALUE,Float.MAX_VALUE);
		}

		if( minValue != -Float.MAX_VALUE && maxValue != Float.MAX_VALUE )
			PixelMath.boundImage(output, minValue, maxValue);
	}

	/**
	 * <p>
	 * Performs a single level wavelet transform.
	 * </p>
	 *
	 * @param desc Description of the wavelet.
	 * @param input Input image. Not modified.
	 * @param output Where the wavelet transform is written to. Modified.
	 * @param storage Optional storage image.  Should be the same size as output image. If null then
	 * an image is declared internally.
	 */
	public static void transform1( WaveletDescription<WlCoef_I32> desc ,
								   ImageSInt32 input , ImageSInt32 output ,
								   ImageSInt32 storage )
	{
		UtilWavelet.checkShape(input,output);

		WlCoef_I32 coef = desc.getForward();

		if( output.width < coef.scaling.length || output.width < coef.wavelet.length )
			throw new IllegalArgumentException("Wavelet is too large for provided image.");
		if( output.height < coef.scaling.length || output.height < coef.wavelet.length )
			throw new IllegalArgumentException("Wavelet is too large for provided image.");
		storage = InputSanityCheck.checkDeclare(output, storage);

		// the faster routines can only be run on images which are not too small
		int minSize = Math.max(coef.getScalingLength(),coef.getWaveletLength())*3;

		if( input.getWidth() <= minSize || input.getHeight() <= minSize ) {
			ImplWaveletTransformNaive.horizontal(desc.getBorder(),coef,input,storage);
			ImplWaveletTransformNaive.vertical(desc.getBorder(),coef,storage,output);
		} else {
			ImplWaveletTransformInner.horizontal(coef,input,storage);
			ImplWaveletTransformBorder.horizontal(desc.getBorder(),coef,input,storage);
			ImplWaveletTransformInner.vertical(coef,storage,output);
			ImplWaveletTransformBorder.vertical(desc.getBorder(),coef,storage,output);
		}
	}

	/**
	 * <p>
	 * Performs a level N wavelet transform using the fast wavelet transform (FWT).
	 * </p>
	 *
	 * <p>To save memory the input image is used to store intermediate results and is modified.</p>
	 *
	 * @param desc Description of the wavelet.
	 * @param input Input image and is used as internal workspace. Modified.
	 * @param output Where the multilevel wavelet transform is written to. Modified.
	 * @param storage Optional storage image.  Should be the same size as output image. If null then
	 * an image is declared internally.
	 * @param numLevels Number of levels which should be computed in the transform.
	 */
	public static void transformN( WaveletDescription<WlCoef_I32> desc ,
								   ImageSInt32 input , ImageSInt32 output ,
								   ImageSInt32 storage ,
								   int numLevels )
	{
		if( numLevels == 1 ) {
			transform1(desc,input,output, storage);
			return;
		}

		UtilWavelet.checkShape(desc.getForward(),input,output,numLevels);
		storage = InputSanityCheck.checkDeclare(output, storage);
		// modify the shape of a temporary image not the original
		storage = storage.subimage(0,0,output.width,output.height, null);
		storage.subImage = false;

		transform1(desc,input,output, storage);

		for( int i = 2; i <= numLevels; i++ ) {
			int width = output.width/2;
			int height = output.height/2;
			width += width%2;
			height += height%2;

			input = input.subimage(0,0,width,height, null);
			output = output.subimage(0,0,width,height, null);
			input.setTo(output);

			// transform the scaling image and save the results in the output image
			storage.reshape(width,height);
			transform1(desc,input,output,storage);
		}
	}

	/**
	 * <p>
	 * Performs a single level inverse wavelet transform. Do not pass in a whole image which has been
	 * transformed by a multilevel transform.  Just the relevant sub-image.
	 * </p>
	 *
	 * @param desc Description of the inverse wavelet.
	 * @param input Input wavelet transform. Not modified.
	 * @param output Reconstruction of original image. Modified.
	 * @param storage Optional storage image.  Should be the same size as the input image. If null then
	 * an image is declared internally.
	 * @param minValue Minimum allowed pixel value
	 * @param maxValue Maximum allowed pixel value
	 */
	public static void inverse1( WaveletDescription<WlCoef_I32> desc ,
								 ImageSInt32 input , ImageSInt32 output ,
								 ImageSInt32 storage , int minValue , int maxValue )
	{
		UtilWavelet.checkShape(output,input);
		WlCoef_I32 coef = desc.getForward();
		if( output.width < coef.scaling.length || output.width < coef.wavelet.length )
			throw new IllegalArgumentException("Wavelet is too large for provided image.");
		if( output.height < coef.scaling.length || output.height < coef.wavelet.length )
			throw new IllegalArgumentException("Wavelet is too large for provided image.");
		storage = InputSanityCheck.checkDeclare(input, storage);

		// the faster routines can only be run on images which are not too small
		int minSize = Math.max(coef.getScalingLength(),coef.getWaveletLength())*3;

		if( output.getWidth() <= minSize || output.getHeight() <= minSize ) {
			ImplWaveletTransformNaive.verticalInverse(desc.getBorder(),desc.getInverse(),input,storage);
			ImplWaveletTransformNaive.horizontalInverse(desc.getBorder(),desc.getInverse(),storage,output);
		} else {
			ImplWaveletTransformInner.verticalInverse(desc.getInverse().getInnerCoefficients(),input,storage);
			ImplWaveletTransformBorder.verticalInverse(desc.getBorder(),desc.getInverse(),input,storage);
			ImplWaveletTransformInner.horizontalInverse(desc.getInverse().getInnerCoefficients(),storage,output);
			ImplWaveletTransformBorder.horizontalInverse(desc.getBorder(),desc.getInverse(),storage,output);
		}

		if( minValue != Integer.MIN_VALUE && maxValue != Integer.MAX_VALUE )
			PixelMath.boundImage(output,minValue,maxValue);
	}

	/**
	 * <p>Performs a level N inverse fast wavelet transform (FWT).</p>
	 *
	 * <p>To save memory the input image is used to store intermediate results and is modified.</p>
	 *
	 * @param desc Description of the inverse wavelet.
	 * @param input Input wavelet transform and is used as internal workspace. Modified.
	 * @param output Reconstruction of original image. Modified.
	 * @param storage Optional storage image.  Should be the same size as the input image. If null then
	 * an image is declared internally.
	 * @param numLevels Number of levels in the transform.
	 * @param minValue Minimum allowed pixel value
	 * @param maxValue Maximum allowed pixel value
	 */
	public static void inverseN( WaveletDescription<WlCoef_I32> desc ,
								 ImageSInt32 input , ImageSInt32 output ,
								 ImageSInt32 storage,
								 int numLevels ,
								 int minValue , int maxValue)
	{
		if( numLevels == 1 ) {
			inverse1(desc,input,output, storage,minValue,maxValue);
			PixelMath.boundImage(output, minValue, maxValue);
			return;
		}

		UtilWavelet.checkShape(desc.getForward(),output,input,numLevels);
		storage = InputSanityCheck.checkDeclare(input, storage);
		// modify the shape of a temporary image not the original
		storage = storage.subimage(0,0,input.width,input.height, null);
		storage.subImage = false;

		int width,height;

		int scale = UtilWavelet.computeScale(numLevels);
		width = input.width/scale;
		height = input.height/scale;
		width += width%2;
		height += height%2;

		ImageSInt32 levelIn = input.subimage(0,0,width,height, null);
		ImageSInt32 levelOut = output.subimage(0,0,width,height, null);
		storage.reshape(width,height);
		inverse1(desc,levelIn,levelOut, storage,Integer.MIN_VALUE,Integer.MAX_VALUE);

		for( int i = numLevels-1; i >= 1; i-- ) {
			// copy the decoded segment into the input
			levelIn.setTo(levelOut);
			if( i > 1 ) {
				scale /= 2;
				width = input.width/scale;
				height = input.height/scale;
				width += width%2;
				height += height%2;

				storage.reshape(width,height);
				levelIn = input.subimage(0,0,width,height, null);
				levelOut = output.subimage(0,0,width,height, null);
			} else {
				levelIn = input;
				levelOut = output;
			}

			storage.reshape(levelIn.width,levelIn.height);
			inverse1(desc,levelIn,levelOut, storage,Integer.MIN_VALUE,Integer.MAX_VALUE);
		}

		if( minValue != Integer.MIN_VALUE && maxValue != Integer.MAX_VALUE )
			PixelMath.boundImage(output, minValue, maxValue);
	}


}
